/* 
 * Annotated 2D axes.
 *
 * Copyright (c) Sergey B. Berezin (s_berezin@cs.msu.su), 2002, 2003.
 *
 * You may freely distribute or modify this source code retaining the 
 * above copyright notice.
 *
 * This software is provided "as-is" and without guarantee or warrantee 
 * of any kind, expressed or implied :)
 */

using System;
using System.Drawing;

using OpenGL;
using ManagedGL;
using ManagedGL.Fonts;
using UVS.Base;

namespace UVS.UI {
	
	public class Axes2dLayer : Layer2d {

		protected Color axisColor = Color.FromArgb(0,0,0);
		protected bool drawGrid = false;
		protected RectangleD axis = new RectangleD(-0.01,-0.01,0.01,0.01);
		protected GLFont font;
		protected int fontSize;

		public RectangleD Axes {
			get {
				return axis;
			}
			set {
				axis = value;
				if(ContextAttached)
					view.UpdateAllLayers();
			}
		}

		protected RectangleD GetVisibleAxis(Viewport2d viewport) {
			RectangleD d0 = viewport.EntireDomain;
			RectangleD d1 = viewport.Visible;			
			return new RectangleD(axis.Xmin + axis.Width*(d1.Xmin - d0.Xmin)/d0.Width,
				axis.Ymin + axis.Height*(d1.Ymin - d0.Ymin)/d0.Height,
				axis.Xmin + axis.Width*(d1.Xmax - d0.Xmin)/d0.Width,
				axis.Ymin + axis.Height*(d1.Ymax - d0.Ymin)/d0.Height);
		}

		public override void OnContextAttached() {
			base.OnContextAttached();
			fontSize = 10;
			Font gdiFont = new Font("Tahoma",fontSize,FontStyle.Bold,GraphicsUnit.Pixel);
			font = GLFont.Create(gdiFont);
		}

		public override void OnContextDetached() {
			font.Dispose();
			base.OnContextDetached();
		}

		public override void OnRender(Viewport2d viewport) {

			// Render coordinate axis
			int x0 = viewport.GetScreenX(viewport.Xmin);
			int y0 = viewport.GetScreenY(viewport.Ymin);
			int x1 = viewport.GetScreenX(viewport.Xmax);
			int y1 = viewport.GetScreenY(viewport.Ymax);
			GL.glBegin(GL.GL_LINES);
			GL.glColor(axisColor);
			GL.glVertex2i(x0,y0);
			GL.glVertex2i(x1+10,y0);
			GL.glVertex2i(x1+10,y0);
			GL.glVertex2i(x1,y0+5);
			GL.glVertex2i(x1+10,y0);
			GL.glVertex2i(x1,y0-5);
			GL.glVertex2i(x0,y0);
			GL.glVertex2i(x0,y1+10);
			GL.glVertex2i(x0,y1+10);
			GL.glVertex2i(x0+5,y1);
			GL.glVertex2i(x0,y1+10);
			GL.glVertex2i(x0-5,y1);
			if(drawGrid) {
				GL.glColor3d(0.5f,0.5f,0.5f);
				GL.glVertex2i(x0,y1);
				GL.glVertex2i(x1,y1);
				GL.glVertex2i(x1,y1);
				GL.glVertex2i(x1,y0);
			}
			GL.glEnd();
			// Do not annotate axes if viewport is empty
			if(viewport.IsEmpty)
				return;
			// Annotate X axis
			RectangleD axis = GetVisibleAxis(viewport);
			double axisMin,axisMax;
			int precision = GetAxisLimits(axis.Xmin,axis.Xmax,out axisMin,out axisMax);
			int[] numTicks = { 11,6,5,3,2 };
			int[] textWidth = new int[11]; // Approximate text labels width (in pixels)
			double[] axisX = new double[11];
			int tickCount = 0;
			for(int i = 0;i<numTicks.Length;i++) {
				int totalWidth = 0;
				for(int j = 0;j<numTicks[i];j++) {
					axisX[j] = axisMin + j*(axisMax-axisMin)/(numTicks[i]-1);
					string text = FormatDouble(axisX[j],(i == 2)?(precision-1):(precision));
					textWidth[j] = (int)(fontSize*font.GetTextWidth(text));
					totalWidth += textWidth[j] + 2*fontSize;
				}
				if(totalWidth < x1-x0) {
					tickCount = numTicks[i];
					break;
				}
			}
			if(tickCount > 0) {
				int outputWidth = x1-x0-textWidth[0]/2-textWidth[tickCount-1]/2;
				GL.glBegin(GL.GL_LINES);
				for(int j = 0;j<tickCount;j++) {
					double viewX = viewport.EntireDomain.Xmin + (axisX[j] - axis.Xmin)*viewport.EntireDomain.Width/axis.Width;
					int windowX = viewport.GetScreenX(viewX);
					GL.glVertex2i(windowX,(drawGrid)?(y1):(y0+5));
					GL.glVertex2i(windowX,y0-3);
				}
				GL.glEnd();
				font.BeginTextOut(axisColor);
				GL.glMatrixMode(GL.GL_MODELVIEW);
				GL.glPushMatrix();
				for(int jj = 0;jj<tickCount;jj++) {
					GL.glLoadIdentity();
					GL.glTranslated(textWidth[0]/2 + x0 + jj*outputWidth/(tickCount-1) - textWidth[jj]/2,
						y0+viewport.Margin-5-fontSize,0);
					GL.glScaled(font.BestScaleFactor,font.BestScaleFactor,0);
					font.TextOut(FormatDouble(axisX[jj],(tickCount == 5)?(precision-1):(precision)));
				}
				GL.glPopMatrix();
				font.EndTextOut();
			}
			// Annotate Y axis
			precision = GetAxisLimits(axis.Ymin,axis.Ymax,out axisMin,out axisMax);
			tickCount = 0;
			double[] axisY = new double[11];
			for(int i = 0;i<numTicks.Length;i++) {
				int totalHeight = 0;
				for(int j = 0;j<numTicks[i];j++) {
					axisY[j] = axisMin + j*(axisMax-axisMin)/(numTicks[i]-1);
					string text = FormatDouble(axisY[j],(i == 2)?(precision-1):(precision));
					textWidth[j] = (int)(fontSize*font.GetTextWidth(text));
					totalHeight += textWidth[j] + 2*fontSize;
				}
				if(totalHeight < y1-y0) {
					tickCount = numTicks[i];
					break;
				}
			}
			if(tickCount > 0) {
				int outputHeight = y1-y0-textWidth[0]/2-textWidth[tickCount-1]/2;
				GL.glBegin(GL.GL_LINES);
				for(int j = 0;j<tickCount;j++) {
					double viewY = viewport.EntireDomain.Ymin + (axisY[j] - axis.Ymin)*viewport.EntireDomain.Height/axis.Height;
					int windowY = viewport.GetScreenY(viewY);
					GL.glVertex2i(x0-3,windowY);
					GL.glVertex2i((drawGrid)?(x1):(x0+5),windowY);
				}
				GL.glEnd();
				font.BeginTextOut(axisColor);
				GL.glPushMatrix();
				for(int jj = 0;jj<tickCount;jj++) {
					GL.glLoadIdentity();
					GL.glTranslated(x0 - 3 - fontSize,
						textWidth[0]/2 + y0 + jj*outputHeight/(tickCount-1) - textWidth[jj]/2,0);							
					GL.glScaled(font.BestScaleFactor,font.BestScaleFactor,0);
					GL.glRotated(90.0,0,0,1);
					font.TextOut(FormatDouble(axisY[jj],(tickCount == 5)?(precision-1):(precision)));
				}
				GL.glPopMatrix();
				font.EndTextOut();
			}
		}

		protected static double CeilN(double x,int n) {
			double n10 = Math.Pow(10,n);
			return (Math.Ceiling(x/n10))*n10;
		}

		protected static double FloorN(double x,double n) {
			double n10 = Math.Pow(10,n);
			return (Math.Floor(x/n10))*n10;
		}

		protected static int GetAxisLimits(double min,double max,out double axisMin,out double axisMax) {
			int n = (int)Math.Ceiling(Math.Log10(max-min));
			while(true) {
				double tempMin = CeilN(min,n);
				double tempMax = FloorN(max,n);
				if(tempMin < tempMax && (max-min)/(tempMax-tempMin)<1.1 /*|| tempMax - tempMin < 1e-8*/) {
					axisMin = tempMin;
					axisMax = tempMax;
					break;
				}
				n--;
			}
			return n-1;
		}

		protected static string FormatDouble(double v,int precision) {
			/*	if(nPrecision < 0)
					sprintf(szBuffer,"%.*lf",-nPrecision,dValue);
				else if(nPrecision < 3)
					sprintf(szBuffer,"%.0lf",dValue);
				else 
					sprintf(szBuffer,"%.1e",dValue);
				return szBuffer;*/
			return v.ToString();
		}
	}
}